(*)Redis 实战应用之分布式 Session 共享

前言

  在传统的单体应用中,所有用户请求都由同一台服务器处理,会话(Session)信息通常保存在服务器内存中即可。
  客户端第一次登录后,服务端会创建一个 Session 并生成 SessionId,保存在服务端内存中,同时将 SessionId 通过 Cookie 返回给客户端。之后客户端的每次请求都会携带该 SessionId,服务端即可识别用户的登录状态。

  然而,随着业务规模扩大和访问量增加,单台服务器已无法承受高并发请求,我们往往会采用 Nginx + 多台应用服务器 的负载均衡方式进行水平扩展。
  此时,Session 共享问题便随之出现。

问题分析:为什么会出现 Session 丢失?

  假设现在有两台后端服务器:

  • Server A
  • Server B

  前端请求通过 Nginx 负载均衡随机分配给这两台服务器。

  1. 用户第一次登录请求被分配到 Server A
  2. Server A 处理登录逻辑,生成 SessionId = ABC123 并保存在本机内存;
  3. 响应返回后,浏览器在 Cookie 中保存该 SessionId;
  4. 用户的第二次请求被 Nginx 分配到 Server B
  5. Server B 收到请求时,会去本机内存中查找 SessionId = ABC123;
  6. 然而 Server B 并没有这个 Session,因此无法识别用户身份。

结果就是:用户会被系统强制登出,需要重新登录。

  这种现象即为Session 不共享问题。在分布式架构中,每台服务器都有独立的内存空间,如果 Session 保存在本地,就会出现用户登录态无法跨服务器识别的情况。

解决方案:分布式 Session

  既然问题的根本在于 Session 只保存在本机内存中,那么解决方案自然是:

将 Session 从本地内存中“抽离”,放到一个所有服务器都能访问的公共存储中

  常见的共享方案包括:

方案 优点 缺点
Session 粘滞(Sticky Session) 配置简单(Nginx 层绑定同一用户请求到同一服务器) 无法应对服务器宕机、扩容不灵活
Session 复制 各节点自动同步 Session 实现复杂,扩展性差
集中式 Session 存储(Redis、Memcached) 高性能、易扩展、实现简单 需依赖外部缓存系统

  在实际生产中,将 Session 存储到 Redis 是最常见且稳定的方案。

Redis 分布式 Session 实现方案

  Redis 作为高性能的内存数据库,支持多种数据结构、持久化、主从同步与集群部署,非常适合用作分布式 Session 存储。

实现步骤

1. 登录时创建 Session

  1. 用户登录成功后,服务端生成一个随机的 Token(可用 UUID 生成)。
1
String token = UUID.randomUUID().toString();
  1. 将用户信息(如 userId、username 等)保存到 Redis:
1
redisTemplate.opsForValue().set("session:" + token, userInfo, 30, TimeUnit.MINUTES);
  1. 将该 Token 返回给客户端,保存在 Cookie 或 Header 中。

2. 请求拦截与 Session 校验

  每次用户请求接口时:

  1. 前端携带 Token(Cookie 或 Header 中传递)。

  2. 服务端通过拦截器或过滤器读取 Token。

  3. 校验 Redis 中是否存在对应的 Session:

1
2
3
4
5
6
String token = request.getHeader("Authorization");
UserInfo user = (UserInfo) redisTemplate.opsForValue().get("session:" + token);
if (user == null) {
// Session 过期或无效
throw new UnauthenticatedException("登录已过期,请重新登录");
}
  1. 如果存在,则刷新 Redis 过期时间(保持用户活跃登录状态)。

实战案例:Spring Boot + Redis 实现分布式 Session

1. 环境准备

  • Spring Boot 3.x
  • Redis 7.x
  • Nginx(负载均衡)

2. 项目配置示例

application.yml

1
2
3
4
5
spring:
redis:
host: 192.168.10.10
port: 6379
timeout: 3000

3. 登录接口实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@RestController
@RequestMapping("/auth")
public class AuthController {

@Autowired
private StringRedisTemplate redisTemplate;

@PostMapping("/login")
public ResponseEntity<?> login(@RequestBody LoginRequest req) {
// 模拟登录校验
if ("admin".equals(req.getUsername()) && "123456".equals(req.getPassword())) {
String token = UUID.randomUUID().toString();
redisTemplate.opsForValue().set("session:" + token, req.getUsername(), 30, TimeUnit.MINUTES);
return ResponseEntity.ok(Map.of("token", token));
}
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("用户名或密码错误");
}
}

4. 拦截器实现 Session 校验

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Component
public class AuthInterceptor implements HandlerInterceptor {

@Autowired
private StringRedisTemplate redisTemplate;

@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
throws Exception {
String token = request.getHeader("Authorization");
if (token == null || !Boolean.TRUE.equals(redisTemplate.hasKey("session:" + token))) {
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
response.getWriter().write("登录已过期,请重新登录");
return false;
}
// 刷新过期时间
redisTemplate.expire("session:" + token, 30, TimeUnit.MINUTES);
return true;
}
}

5. 注册拦截器

1
2
3
4
5
6
7
8
9
10
11
12
@Configuration
public class WebConfig implements WebMvcConfigurer {
@Autowired
private AuthInterceptor authInterceptor;

@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(authInterceptor)
.addPathPatterns("/**")
.excludePathPatterns("/auth/login");
}
}

效果验证

  在 Nginx 中配置两台服务器轮询访问:

1
2
3
4
upstream backend {
server 192.168.10.101:8080;
server 192.168.10.102:8080;
}

  用户登录后访问多次接口,即使请求被分配到不同服务器,也不会丢失登录态,说明 Session 已实现分布式共享。

架构示意图

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
     ┌────────────┐
│ Client │
└─────┬──────┘


┌────────────┐
│ Nginx │ ← 负载均衡
└────┬───────┘
┌────────┴────────┐
│ │
▼ ▼
┌───────┐ ┌───────┐
│ServerA│ │ServerB│
│(App) │ │(App) │
└───────┘ └───────┘
│ │
└──────┬──────────┘

┌────────────┐
│ Redis │ ← 统一存储 Session
└────────────┘

总结

  本文从传统单体 Session 管理出发,逐步分析了分布式环境下的 Session 共享问题,并结合 Redis 给出了完整解决方案与实战案例,希望能帮助你在分布式架构实践中少踩坑。

文章信息

时间 说明
2019-04-20 初稿
0%